import _ from 'lodash'
import {BaseEntity, DiagramModel, LinkModel, NodeModel, PointModel} from 'react-js-diagrams'
import {FlowConst} from "../../Const";

/**
 * Passed as a parameter to the DiagramWidget
 */
export class FlowDiagramEngine extends BaseEntity {
    constructor () {
        super()
        this.diagramModel = new DiagramModel()
        this.nodeFactories = {}
        this.linkFactories = {}
        this.instanceFactories = {}
        this.linkInstanceFactory = null
        this.canvas = null
        this.paintableWidgets = null
        this.forceUpdate = () => {}
    }

    clearRepaintEntities () {
        this.paintableWidgets = null
    }

    enableRepaintEntities (entities) {
        this.paintableWidgets = {}
        entities.forEach(entity => {
            // If a node is requested to repaint, add all of its links
            if (entity instanceof NodeModel) {
                _.forEach(entity.getPorts(), port => {
                    _.forEach(port.getLinks(), link => {
                        this.paintableWidgets[link.getID()] = true
                    })
                })
            }

            if (entity instanceof PointModel) {
                this.paintableWidgets[entity.getLink().getID()] = true
            }

            this.paintableWidgets[entity.getID()] = true
        })
    }

    canEntityRepaint (baseModel) {
        // No rules applied, allow repaint
        if (this.paintableWidgets === null) {
            return true
        }

        return this.paintableWidgets[baseModel.getID()] !== undefined
    }

    setCanvas (canvas) {
        this.canvas = canvas
    }

    setDiagramModel (model) {
        this.diagramModel = model
    }

    setForceUpdate (forceUpdate) {
        this.forceUpdate = forceUpdate
    }

    getDiagramModel () {
        return this.diagramModel
    }

    getNodeFactories () {
        return this.nodeFactories
    }

    getLinkFactories () {
        return this.linkFactories
    }

    getInstanceFactory (className) {
        return this.instanceFactories[className]
    }

    registerInstanceFactory (factory) {
        this.instanceFactories[factory.getName()] = factory
        // Check for a link instance factory to be used when creating new links via drag
        if (factory.getInstance() instanceof LinkModel) {
            this.linkInstanceFactory = factory
        }
    }

    registerNodeFactory (factory) {
        this.nodeFactories[factory.getType()] = factory
        this.itterateListeners(listener => {
            listener.nodeFactoriesUpdated()
        })
    }

    registerLinkFactory (factory) {
        this.linkFactories[factory.getType()] = factory
        this.itterateListeners(listener => {
            listener.linkFactoriesUpdated()
        })
    }

    getFactoryForNode (node) {
        if (this.nodeFactories[node.getType()]) {
            return this.nodeFactories[node.getType()]
        }
        console.log(`Cannot find widget factory for node of type: [${node.getType()}]`); // eslint-disable-line
        return null
    }

    getFactoryForLink (link) {
        if (this.linkFactories[link.getType()]) {
            return this.linkFactories[link.getType()]
        }
        console.log(`Cannot find widget factory for link of type: [${link.getType()}]`); // eslint-disable-line
        return null
    }

    generateWidgetForLink (link) {
        const linkFactory = this.getFactoryForLink(link)
        if (!linkFactory) {
            throw `Cannot find link factory for link: ${link.getType()}`
        }
        return linkFactory.generateReactWidget(this, link)
    }

    generateWidgetForNode (node) {
        const nodeFactory = this.getFactoryForNode(node)
        if (!nodeFactory) {
            throw `Cannot find widget factory for node: ${node.getType()}`
        }
        return nodeFactory.generateReactWidget(this, node)
    }

    getRelativeMousePoint (event) {
        const point = this.getRelativePoint(event.pageX, event.pageY)
        return {
            x: (point.x / (this.diagramModel.getZoomLevel() / 100.0)) - this.diagramModel.getOffsetX(),
            y: (point.y / (this.diagramModel.getZoomLevel() / 100.0)) - this.diagramModel.getOffsetY(),
        }
    }

    getRelativePoint (x, y) {
        const canvasRect = this.canvas.getBoundingClientRect()
        return { x: x - canvasRect.left, y: y - canvasRect.top }
    }

    getNodePortElement (port) {
        let selector = this.canvas.querySelector(
            `.realPort[data-nodeid="${port.getParent().getID()}"]`
        )
        if(selector != null){
            return selector
        }

        selector = this.canvas.querySelector(
            `.port[data-name="${port.getName()}"][data-nodeid="${port.getParent().getID()}"]`
        )
        if (selector === null) {
            throw `Cannot find Node Port element with nodeID: [${port.getParent().getID()}] and name: [${port.getName()}]`
        }
        return selector
    }

    getPortCenter (port, defaultPoint) {
        if(!port){
            return defaultPoint
        }
        const {rel, width, height} = this.getReactSize(port)
        return this.getCenterPoint(rel, width, height)
    }

    getCenterPoint (rel, width, height) {
        const x = (width / 2.0) + rel.x / (this.diagramModel.getZoomLevel() / 100.0) -
            this.diagramModel.getOffsetX()
        const y = (height / 2.0) + rel.y / (this.diagramModel.getZoomLevel() / 100.0) -
            this.diagramModel.getOffsetY()

        return {
            x,
            y,
        }
    }

    getSourceJointPoint (port, targetCenterPoint) {
        const {rel, width, height} = this.getReactSize(port)

        const sourceCenterPoint = this.getCenterPoint(rel, width, height)

        let horizontalAdjust = width/2.0
        let verticalAdjust = height/2.0

        let adjusts = this.getProperJointSourceAdjustsFromCenter(sourceCenterPoint, targetCenterPoint, width, horizontalAdjust, height, verticalAdjust)

        return {
            x : sourceCenterPoint.x + adjusts.x,
            y : sourceCenterPoint.y + adjusts.y,
        }
    }


    getTargetJointPoint (port, sourceCenterPoint) {
        const {rel, width, height} = this.getReactSize(port)
        const targetCenterPoint = this.getCenterPoint(rel, width, height)

        let horizontalAdjust = -1 * width/2.0
        let verticalAdjust = -1 * height/2.0

        let adjusts = this.getProperJointTargetAdjustsFromCenter(sourceCenterPoint, targetCenterPoint, width, horizontalAdjust, height, verticalAdjust)

        return {
            x : targetCenterPoint.x + adjusts.x,
            y : targetCenterPoint.y + adjusts.y,
        }
    }

    getProperJointSourceAdjustsFromCenter (sourceCenterPoint, targetCenterPoint, width, horizontalAdjust, height, verticalAdjust) {
        const __ret = this.determinceDirection(width, height, sourceCenterPoint, targetCenterPoint, horizontalAdjust, verticalAdjust)
        let halfWidth = __ret.halfWidth
        let halfHeight = __ret.halfHeight
        horizontalAdjust = __ret.horizontalAdjust
        verticalAdjust = __ret.verticalAdjust

        if (Math.abs(sourceCenterPoint.x - targetCenterPoint.x) <= halfWidth) {
            horizontalAdjust = 0.0
        }

        if (Math.abs(sourceCenterPoint.y - targetCenterPoint.y) <= halfHeight) {
            verticalAdjust = 0.0
        }

        if(Math.abs(sourceCenterPoint.x - targetCenterPoint.x) > halfWidth ){
            verticalAdjust = 0.0
        }else if (Math.abs(sourceCenterPoint.y - targetCenterPoint.y) > halfHeight) {
            horizontalAdjust = 0.0
        }

        return {
            x: horizontalAdjust,
            y: verticalAdjust,
        }
    }
    getProperJointTargetAdjustsFromCenter (sourceCenterPoint, targetCenterPoint, width, horizontalAdjust, height, verticalAdjust) {
        const __ret = this.determinceDirection(width, height, sourceCenterPoint, targetCenterPoint, horizontalAdjust, verticalAdjust)
        let halfWidth = __ret.halfWidth
        let halfHeight = __ret.halfHeight
        horizontalAdjust = __ret.horizontalAdjust
        verticalAdjust = __ret.verticalAdjust

        if (Math.abs(sourceCenterPoint.y - targetCenterPoint.y) <= halfHeight + FlowConst.ARROW_SIZE ) {
            verticalAdjust = 0.0
        }else if (Math.abs(sourceCenterPoint.x - targetCenterPoint.x) <= halfWidth + FlowConst.ARROW_SIZE ) {
            horizontalAdjust = 0.0
        }else if (Math.abs(sourceCenterPoint.y - targetCenterPoint.y) > halfHeight + FlowConst.ARROW_SIZE ) {
            horizontalAdjust = 0.0
        } else if(Math.abs(sourceCenterPoint.x - targetCenterPoint.x) > halfWidth + FlowConst.ARROW_SIZE){
            verticalAdjust = 0.0
        }

        return {
            x: horizontalAdjust,
            y: verticalAdjust,
        }
    }

    determinceDirection (width, height, sourceCenterPoint, targetCenterPoint, horizontalAdjust, verticalAdjust) {
        let halfWidth = width / 2.0
        let halfHeight = height / 2.0
        if (sourceCenterPoint.x - targetCenterPoint.x > halfWidth) {
            horizontalAdjust = -1 * horizontalAdjust
        }

        if (sourceCenterPoint.y - targetCenterPoint.y > halfHeight) {
            verticalAdjust = -1 * verticalAdjust
        }
        return {halfWidth, halfHeight, horizontalAdjust, verticalAdjust}
    }

    getReactSize (port) {
        const sourceElement = this.getNodePortElement(port)
        const sourceRect = sourceElement.getBoundingClientRect()
        const rel = this.getRelativePoint(sourceRect.left, sourceRect.top)
        const width = sourceElement.offsetWidth ? sourceElement.offsetWidth : sourceElement.width.baseVal.value
        const height = sourceElement.offsetHeight ? sourceElement.offsetHeight : sourceElement.height.baseVal.value
        return {rel, width, height}
    }
}
